Mustersuche¶
3.1 Korrelation in Signalen¶
import cv2 as cv
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import statsmodels.api as sm
C:\Program Files\Python311\Lib\site-packages\matplotlib\projections\__init__.py:63: UserWarning: Unable to import Axes3D. This may be due to multiple versions of Matplotlib being installed (e.g. as a system package and as a pip package). As a result, the 3D projection is not available.
warnings.warn("Unable to import Axes3D. This may be due to multiple versions of "
Daten: Ich verwende hier Wetterdaten von Athen, die die Tagestemperatur in Grad Celsius enthalten. Die Daten sind in einem CSV-Format gespeichert und enthalten die Temperaturwerte für jeden Tag von 2019 bis 2024. Die Daten zwischen dem 01.01.2023 und dem 17.05.2024 sind von dieser Webseite gescraped: https://www.accuweather.com/en/gr/athens/182536/may-weather/182536?year=2024. Die Daten von 2019 bis 2022 wurden mit hilfe der Originaldaten von ChatGPT 4o generiert.
weather = pd.read_csv('Extended_Weather_Past_Data_with_Seasonal_Variation.csv', sep=',', decimal='.')
weather['Datum'] = pd.to_datetime(weather['Datum'], yearfirst=True)
weather.head()
| Unnamed: 0 | Datum | Wochentag | Tagestemperatur | Nachttemperatur | |
|---|---|---|---|---|---|
| 0 | 0 | 2019-05-02 | Thursday | 21 | 10 |
| 1 | 1 | 2019-05-03 | Friday | 23 | 14 |
| 2 | 2 | 2019-05-04 | Saturday | 25 | 12 |
| 3 | 3 | 2019-05-05 | Sunday | 20 | 14 |
| 4 | 4 | 2019-05-06 | Monday | 24 | 13 |
Die Daten bestehen jeweils aus einem Datum, dem Wochentag und einer durchschnittlichen Tages- bzw. Nachttemperatur in Grad Celsius. In diesem Fall interessieren wir uns nur für die Tagestemperatur.
plt.plot(weather['Datum'], weather['Tagestemperatur'])
plt.title('Temperaturen in Athen')
plt.xlabel('Tage')
plt.ylabel('Amplitude')
selected_dates = pd.to_datetime(['2019-03-01', '2019-06-01', '2019-09-01', '2019-12-01', '2020-03-01', '2020-06-01', '2020-09-01', '2020-12-01', '2021-03-01', '2021-06-01', '2021-09-01', '2021-12-01', '2022-03-01', '2022-06-01', '2022-09-01', '2022-12-01', '2023-03-01', '2023-06-01', '2023-09-01', '2023-12-01', '2024-03-01'])
plt.xticks(selected_dates, selected_dates.strftime('%Y-%m-%d'), rotation=45)
plt.show()
Die geplotteten Daten zeigen die Tagestemperatur in Athen von 2019 bis 2024. Die Daten zeigen eine saisonale Variation, bei der die Temperaturen im Sommer höher sind als im Winter. Auch zu erkennen ist, dass die von ChatGPT generierten Daten mehr Variation innerhalb einer Saison aufweisen, jedoch weniger hohe bzw. tiefe Peaks erreichen wie die gescrapten Daten.
Experiment: Am Äquator gibt es keine Jahreszeiten, da die Sonne das ganze Jahr über gleichmäßig scheint. Ein Meteorologe möchte beweisen das seine Heimatstadt Athen nicht am Äquator liegt. Dafür hat er die Temperaturdaten von 2019 bis 2024 gesammelt und möchte nun die saisonale Variation in den Daten analysieren. Treten saisonale Muster in den Daten auf, so kann er beweisen, dass Athen nicht am Äquator liegt. Um saisonale Schwankungen in seinen Daten zu untersuchen verwendet er die Autokorrelation.
lags = len(weather) -1
sm.graphics.tsa.plot_acf(weather["Tagestemperatur"], lags=lags, title="Correlogram")
plt.xlabel("Lags")
plt.ylabel("Autokorrelation")
plt.xlim(0, lags)
plt.show()
Analyse der Autokorrelation:
Der Plot zeigt deutliche Peaks in regelmäßigen Abständen, was auf eine starke saisonale Komponente in den Daten hinweist. Diese Peaks treten etwa alle 365 Tage auf, was dem jährlichen Zyklus der Jahreszeiten entspricht. Die schattierten Bereiche im Plot repräsentieren die Konfidenzintervalle. Werte innerhalb dieser Bereiche sind nicht signifikant von Null verschieden. Die klaren Peaks außerhalb dieser Intervalle zeigen, dass die Autokorrelation zu diesen Zeitpunkten signifikant ist. Die Korrelation nimmt jedoch laufend ab. Dies liegt daran, dass die Temperaturdaten von Jahr zu Jahr variieren und nicht immer exakt gleich sind. Das Experiment bestätigt jedoch die Hypothese des Meteorologen, dass Athen nicht am Äquator liegt, da die Daten saisonale Muster aufweisen.
Was ist Autokorrelation?
Die Autokorrelation misst die Korrelation einer Zeitreihe mit sich selbst zu verschiedenen Zeitverzögerungen (Lags). Sie hilft dabei, Muster und Abhängigkeiten in zeitlichen Daten zu identifizieren, insbesondere periodische oder saisonale Muster. Ein hoher Autokorrelationswert bei einem bestimmten Lag zeigt an, dass die Werte der Zeitreihe mit diesem Lag eine starke Ähnlichkeit aufweisen. Die Autokorrelation ist nützlich in vielen Anwendungsbereichen, darunter Wettervorhersage, Aktienmarktanalyse und Signalverarbeitung, da sie helfen kann, die Struktur und die wiederkehrenden Muster innerhalb der Daten zu verstehen.
$\[ r_k = \frac{\sum_{t=1}^{n-k} (X_t - \bar{X})(X_{t+k} - \bar{X})}{\sum_{t=1}^{n} (X_t - \bar{X})^2} \]$
wobei: $\( X_t \)$ der Wert der Zeitreihe zum Zeitpunkt $\( t \)$ ist, $\( \bar{X} \)$ der Mittelwert der Zeitreihe ist, $\( n \)$ die Anzahl der Beobachtungen in der Zeitreihe ist, $\( k \)$ die Verzögerung (Lag) ist.
Kreuzkorrelation von Temperaturdaten¶
Der Meteorologe findet auf seinem PC noch eine Aufnahme bei welcher er nicht weiss wohin diese gehört. Er möchte herausfinden ob es eine Kopie der bereits existierenden Daten ist. Dafür verwendet er die Kreuzkorrelation.
x_daten = weather["Tagestemperatur"]
y_daten = weather["Tagestemperatur"][1600:1700]
cross_corr = np.correlate(x_daten, y_daten, mode='full')
lags = np.arange(-len(y_daten) + 1, len(x_daten))
# Plotten der Kreuzkorrelation
plt.stem(cross_corr)
plt.xlabel('Lag')
plt.ylabel('Kreuzkorrelation')
plt.title('Kreuzkorrelation zwischen signal und segment')
plt.axvline(np.argmax(cross_corr), color='r', linestyle='--')
plt.show()
# Den Lag mit der höchsten Kreuzkorrelation finden
print("Der beste Lag ist:", np.argmax(cross_corr))
Der beste Lag ist: 1608
Der Meteorologe kann aus diesen Ergebnissen schließen, dass das Segment (Index 1600 bis 1700) eine Kopie eines Teils der gesamten Zeitreihe ist und dass diese Kopie am besten mit einem spezifischen Lag übereinstimmt. Diese Information kann verwendet werden, um die Quelle der Aufnahme zu bestimmen und zu überprüfen, ob es sich tatsächlich um eine Kopie der existierenden Daten handelt.
Kreuzkorrelation: Die Kreuzkorrelation misst die Ähnlichkeit zwischen zwei Zeitreihen als Funktion der Zeitverzögerung (Lag) der einen Reihe relativ zur anderen. Sie hilft dabei, die Verzögerung zu identifizieren, bei der zwei Signale oder Zeitreihen die höchste Korrelation aufweisen. Dies ist besonders nützlich in verschiedenen Anwendungsbereichen wie Signalverarbeitung, Wetteranalyse und Wirtschaftsforschung, um zeitliche Beziehungen und Muster zwischen verschiedenen Datensätzen zu identifizieren.
Die Kreuzkorrelation ist ein mächtiges Werkzeug, um die Übereinstimmung zwischen zwei Signalen zu analysieren und kann verwendet werden, um zeitliche Verschiebungen zu messen und zu verstehen, wie ein Signal auf ein anderes reagiert.
$\[ R_{xy}[k] = \sum_{n=0}^{N-1} x[n] \cdot y[n+k] \]$
wobei: $\( x[n] \)$ der Wert der ersten Zeitreihe zum Zeitpunkt $\( n \)$ ist, $\( y[n+k] \)$ der Wert der zweiten Zeitreihe zum Zeitpunkt $\( n+k \)$ ist, $\( N \)$ die Länge der Zeitreihen ist, $\( k \)$ der Lag ist, der die Verschiebung der zweiten Zeitreihe relativ zur ersten Zeitreihe darstellt.
Rolling Mean¶
Um die robustheit der Kreuzkorrelation zu kontrollieren, kontrolliert der Meteorologe die Kreuzkorrelation mit einem Rolling Mean auf den ursprünglichen Daten.
rolling_mean_3 = weather["Tagestemperatur"].rolling(window=3).mean()
rolling_mean_5 = weather["Tagestemperatur"].rolling(window=5).mean()
rolling_mean_10 = weather["Tagestemperatur"].rolling(window=10).mean()
rolling_mean_20 = weather["Tagestemperatur"].rolling(window=20).mean()
#plot subplot with different rolling means
fig, ax = plt.subplots(5, 1, figsize=(15, 20))
ax[0].plot(weather["Tagestemperatur"], label='Original')
ax[0].set_title('Original')
ax[0].set_xlabel('Tage')
ax[0].set_ylabel('Grad Celsius')
ax[0].legend()
ax[1].plot(rolling_mean_3, label='Rolling Mean (3)')
ax[1].set_title('Rolling Mean (3)')
ax[1].set_xlabel('Tage')
ax[1].set_ylabel('Grad Celsius')
ax[1].legend()
ax[2].plot(rolling_mean_5, label='Rolling Mean (5)')
ax[2].set_title('Rolling Mean (5)')
ax[2].set_xlabel('Tage')
ax[2].set_ylabel('Grad Celsius')
ax[2].legend()
ax[3].plot(rolling_mean_10, label='Rolling Mean (10)')
ax[3].set_title('Rolling Mean (10)')
ax[3].set_xlabel('Tage')
ax[3].set_ylabel('Grad Celsius')
ax[3].legend()
ax[4].plot(rolling_mean_20, label='Rolling Mean (20)')
ax[4].set_title('Rolling Mean (20)')
ax[4].set_xlabel('Tage')
ax[4].set_ylabel('Grad Celsius')
ax[4].legend()
plt.subplots_adjust(hspace=0.3)
plt.show()
Die Temperaturen sehen mit grösserem Window smoother aus. Beim rolling Mean 20 ist der Unterschied zwischen den echten Daten und den generierten Daten deutlich zu sehen. Der Meteorologe möchte nun die Kreuzkorrelation mit den Rolling Means durchführen.
def kreuzkorrelation(ganzeDaten, ausschnittDaten):
cross_corr = np.correlate(ganzeDaten, ausschnittDaten, mode='valid')
# Plotten der Kreuzkorrelation
plt.stem(cross_corr)
plt.xlabel('Lag')
plt.ylabel('Kreuzkorrelation')
plt.title('Kreuzkorrelation zwischen Signal und Ausschnitt')
max_corr_index = np.argmax(cross_corr)
plt.axvline(max_corr_index, color='r', linestyle='--')
plt.show()
print("Der beste Lag ist:", max_corr_index)
return max_corr_index
ausschnittDaten3 = rolling_mean_3[1500:1600]
ausschnittDaten5 = rolling_mean_5[1500:1600]
ausschnittDaten10 = rolling_mean_10[1500:1600]
ausschnittDaten20 = rolling_mean_20[1500:1600]
#subplots with different rolling means
kreuzkorrelation(weather["Tagestemperatur"], ausschnittDaten3)
kreuzkorrelation(weather["Tagestemperatur"], ausschnittDaten5)
kreuzkorrelation(weather["Tagestemperatur"], ausschnittDaten10)
kreuzkorrelation(weather["Tagestemperatur"], ausschnittDaten20)
Der beste Lag ist: 1499
Der beste Lag ist: 1499
Der beste Lag ist: 1499
Der beste Lag ist: 1493
1493
Zur Freude des Meteorologen findet die Kreuzkorrelation auch für veränderte Daten den richtigen Lag. Die Kreuzkorrelation ist also robust gegenüber kleinen Veränderungen in den Daten. Nun möchte der Meteorologe die Kreuzkorrelation noch dem ultimativen Härtetest unterziehen und die Ursprungsdaten mit Noise ergänzen.
noise = np.random.normal(0, 10, len(weather["Tagestemperatur"]))
weather["Tagestemperatur_noise"] = weather["Tagestemperatur"] + noise
plt.plot(weather["Tagestemperatur_noise"])
plt.title('Temperaturen in Athen mit Noise')
plt.xlabel('Tage')
plt.ylabel('Grad Celsius')
plt.show()
Die Daten sehen nun verrauscht aus. Der Meteorologe möchte nun die Kreuzkorrelation mit den verrauschten Daten durchführen.
pc_daten = weather["Tagestemperatur"][1500:1600]
kreuzkorrelation(weather["Tagestemperatur_noise"], pc_daten)
Der beste Lag ist: 1502
1502
Die Kreuzkorrelation funktioniert auch mit verrauschten Daten. Der Meteorologe ist sehr zufrieden mit den Ergebnissen und kann nun den Zeitraum der Daten bestimmen.
Zusammenfassung¶
Der Meteorologe hat erfolgreich die saisonale Variation in den Temperaturdaten von Athen analysiert. Er konnte mithilfe der Autokorrelation zeigen, dass die Daten saisonale Muster aufweisen und somit beweisen, dass Athen nicht auf dem Äquator liegt. Die Kreuzkorrelation half ihm, eine Kopie der Daten zu identifizieren und die Ursprungsdaten mit verschiedenen Rolling Means und verrauschten Daten zu überprüfen. Die Kreuzkorrelation erwies sich als robust gegenüber kleinen Veränderungen in den Daten und funktionierte auch mit verrauschten Daten. Der Meteorologe ist sehr zufrieden mit den Ergebnissen.
3.2 Segmentierung, morphologische Operationen und Objekteigenschaften in Bildern¶
Geschichte: Beim Abfüllen von Oliven in Griechenland geschieht es leider immer wieder, dass grüne, früh geerntete Oliven mit den reiferen schwarzen Oliven vermischt werden. Um diesem Problem entgegenzuwirken wurde ein Forscher beauftragt eine Bildverarbeitungslösung zu finden, die die grünen Oliven von den schwarzen Oliven trennt. Der Forscher hat eine Kamera installiert, die Bilder von den Oliven aufnimmt. Er möchte nun die grünen Oliven von den schwarzen Oliven trennen.
Segmentierung¶
Dieses Bild habe ich selber aufgenommen.
img = cv.imread('Fotos_Oliven/20240530_063031.jpg')
plt.imshow(cv.cvtColor(img, cv.COLOR_BGR2RGB))
plt.gcf().set_size_inches(10, 10)
plt.title('Oliven Originalbild')
plt.show()
Auf dem Bild sind die grünen und schwarzen Oliven zu sehen. Der Forscher möchte nun die grünen Oliven von den schwarzen Oliven trennen. Dafür möchte er die Bildsegmentierung und morphologische Operationen verwenden.
cut_img = img[700:2700, 500:2500]
plt.imshow(cv.cvtColor(cut_img, cv.COLOR_BGR2RGB))
plt.gcf().set_size_inches(10, 10)
plt.title('Cropped Image')
plt.show()
Zuerst hat der Forscher das Bild zugeschnitten, um nur die Oliven zu sehen.
Um die Oliven besser zu segmentieren, konvertiert der Forscher das Bild in den HSV-Farbraum. Der HSV-Farbraum besteht aus drei Komponenten: Hue, Saturation und Value. Der Forscher hofft die Oliven in einem der drei Komponenten besser zu sehen.
# Convert to hsv
hsv = cv.cvtColor(cut_img, cv.COLOR_BGR2HSV)
# plot
fig, ax = plt.subplots(1, 3, figsize=(15, 5))
ax[0].imshow(hsv[:, :, 0], cmap='hsv')
ax[0].set_title('Hue')
ax[0].axis('off')
ax[1].imshow(hsv[:, :, 1], cmap='gray')
ax[1].set_title('Saturation')
ax[1].axis('off')
ax[2].imshow(hsv[:, :, 2], cmap='gray')
ax[2].set_title('Value')
ax[2].axis('off')
plt.show()
Der Forscher hat die Bilder in den HSV-Farbraum konvertiert. Im Saturation- und Value-Kanal sind die Oliven am besten zu erkennen.
Ein alternativer Ansatz ist die Umwandlung des Bildes zu Graustufen. Anschliessend wird die Helligkeit des Bildes erhöht und das Bild invertiert.
from skimage import filters, measure, morphology
from skimage.segmentation import clear_border
img_gray = cv.cvtColor(cut_img, cv.COLOR_BGR2GRAY)
# Make the image brighter
img_gray = cv.add(img_gray, 135)
#convert the image to binary
# Invert the image
img_gray = cv.bitwise_not(img_gray)
plt.imshow(img_gray, cmap='gray')
plt.axis('off')
plt.gcf().set_size_inches(10, 10)
plt.title('Inverted Gray Image')
plt.show()
Das Ergebniss ist fast identisch mit dem Saturation-Kanal des HSV-Farbraums.
Als nächsten Schritt verwendet er die morphologische Operation der Reconstruction.
import numpy as np
from skimage.morphology import reconstruction
seed = np.copy(img_gray)
seed[1:-1, 1:-1] = img_gray.max()
mask = img_gray
filled = reconstruction(seed, mask, method='erosion')
plt.imshow(filled)
plt.axis('off')
plt.gcf().set_size_inches(10, 10)
plt.title('Reconstruction Morph 1 & 2')
plt.show()
Eine Kopie des invertierten Graustufenbildes wird erstellt und als seed gesetzt. Die inneren Bereiche des Seed-Bildes, also alle außer den Randpixeln, werden auf den maximalen Wert des Originalbildes gesetzt. Das bedeutet, dass nur die Randpixel ihre ursprünglichen Werte behalten, während die inneren Pixel auf den höchsten Wert im Bild gesetzt werden. Das Maske-Bild bleibt das unveränderte, invertierte Graustufenbild. Die Funktion reconstruction führt dann eine Rekonstruktion durch Erosion durch. Dabei werden niedrige Intensitätswerte vom Seed-Bild aus verbreitet, bis sie durch die Werte im Maske-Bild begrenzt werden. In diesem Fall bedeutet es, dass die ursprünglichen Strukturen des Bildes erhalten bleiben, aber die inneren Bereiche, die auf den maximalen Wert gesetzt wurden, werden durch die umgebenden Pixelwerte der Maske begrenzt. Dies erzeugt kleine Inseln, in diesem Fall Oliven.
Um die Thresholds der verschiedenen Klassen zu finden, verwendet der Forscher die Multi-Otsu-Methode. Mit der Multi-Otsu-Methode kann ein Bild in mehrere Klassen unterteilt werden, basierend auf den Graustufenwerten der Pixel. Für dieses Problem werden die Oliven in drei Klassen unterteilt: grün, schwarz und Hintergrund.
thresholds = filters.threshold_multiotsu(filled, classes=3)
regions = np.digitize(filled, bins=thresholds)
plt.imshow(regions)
plt.axis('off')
plt.gcf().set_size_inches(10, 10)
plt.title(f'Thresholded Image ,Thresholds: {thresholds}')
plt.show()
Die Multi-Otsu-Methode hat das Bild in drei Klassen unterteilt: grün, schwarz und Hintergrund. Der Hintergrund ist in Klasse 1, die schwarzen Oliven sind in Klasse 2 und die grünen Oliven in Klasse 3. Grundsätzlich scheint die Unterteilung sehr gut funktioniert zu haben. Die untere linke Olive bereitet jedoch ein wenig Probleme. Die eigentlich grüne Olive wird fast zur Hälfte schwarz eingestuft. Der Forscher möchte nun die Klassenbereiche genauer untersuchen.
Multi-Otsu-Methode: Die Multi-Otsu-Methode ist eine Erweiterung der Otsu-Methode, die es ermöglicht, ein Bild in mehrere Klassen zu unterteilen, basierend auf den Graustufenwerten der Pixel. Das grundlegende Prinzip der Multi-Otsu-Methode besteht darin, die Schwellenwerte so zu wählen, dass die Varianz zwischen den Klassen maximiert wird. Die Varianz ist ein Maß für die Streuung der Intensitätswerte und eine hohe zwischenklassen Varianz bedeutet, dass die Klassen möglichst unterschiedlich sind.
# Histogramm of Pixel values
plt.hist(filled.ravel(), bins=256, range=(0, 256), fc='k', ec='k')
plt.title('Histogram of Pixel Values')
plt.xlabel('Pixel Value')
plt.ylabel('Frequency')
# Plot lines for thresholds
for threshold in thresholds:
plt.axvline(threshold, color='r')
plt.show()
Das Histogramm zeigt die Verteilung der Pixelwerte im Bild. Die roten Linien markieren die gefundenen Schwellenwerte. Die Klassenbereiche sind gut voneinander getrennt.
In einem ersten verbesserungsschritt möchte der Forscher die im Ecken falsch erkannten Tellerränder entfernen. Dafür verwendet er die Funktion clear_border.
cleaned_mask = clear_border(regions)
plt.imshow(cleaned_mask)
plt.axis('off')
plt.gcf().set_size_inches(10, 10)
plt.title('Cleared Boarders')
plt.show()
Die Funktion clear_boarder entfernt alle Objekte, die an den Rändern des Bildes liegen. Die Artifakten des Tellerrandes wurden erfolgreich entfernt.
Als nächsten Schritt möchte der Forscher mithilfe von morphologischen Operationen die Oliven besser segmentieren. Dafür verwendet er zuerst eine Erosion.
eroded_mask = morphology.erosion(cleaned_mask, morphology.disk(15))
plt.imshow(eroded_mask)
plt.axis('off')
plt.gcf().set_size_inches(10, 10)
plt.title('Eroded Image Morph 3')
plt.show()
Die Erosion entfernt kleine Artefakte und glättet die Kanten. Dies funktioniert indem für jeden Pixel des Bildes der kleinst vorkommende Wert innerhalb des Strukturelements (Kernel) gesetzt wird. Dies führt dazu, dass das Bild im allgemeinen dunkler wird. Gelbe Inseln sind teilweise verschwunden oder kleiner geworden. Um jede gelbe Olive hat sich einen kleinen grünen Rand gebildet.
Als weiterer Schritt wählt der Forscher eine Dilation.
dilated_mask = morphology.dilation(eroded_mask, morphology.disk(15))
plt.imshow(dilated_mask)
plt.axis('off')
plt.gcf().set_size_inches(10, 10)
plt.title('Dilated Image Morph 4')
plt.show()
Die Kanten sind abgerundeter als im Erosionsbild. Die Dilation erweitert helle Objekte im Bild. Für jeden Pixel des Bildes wird der größte vorkommende Wert innerhalb des Strukturelements (Kernel) gesetzt. Dies führt dazu, dass das Bild im allgemeinen heller wird.
Um die untere rechte Olive zu verbessern, führt der Forscher eine Closing-Operation durch.
closed_mask = morphology.closing(dilated_mask, morphology.diamond(10))
plt.imshow(closed_mask)
plt.axis('off')
plt.gcf().set_size_inches(10, 10)
plt.title('Closed Image Morph 5 & 6')
plt.show()
Die Closing-Operation ist eine Kombination aus einer Dilation und einer Erosion. Zuerst wird eine Dilation durchgeführt, gefolgt von einer Erosion. Dies führt dazu, dass kleine Löcher in den Objekten geschlossen werden und die Kanten glatter werden. Leider hat die Closing-Operation nicht den gewünschten Effekt auf die untere rechte Olive.
Als weiterer Versuch führt der Forscher eine Area-Opening-Operation durch.
open_area_mask = morphology.area_opening(closed_mask, area_threshold=50000)
plt.imshow(open_area_mask)
plt.axis('off')
plt.gcf().set_size_inches(10, 10)
plt.title('Area Opening Morph 7 & 8')
plt.show()
Diese Operation hat nun den gewünschten Effekt auf die untere rechte Olive. Die Olive ist nun vollständig grün. Die Area-Opening-Operation entfernt Objekte, die kleiner sind als ein bestimmter Schwellenwert. In diesem Fall wurden alle Objekte entfernt, die eine Fläche von weniger als 50000 Pixeln haben.
Um die Eigenschaften der Oliven zu extrahieren, verwendet der Forscher die label-Funktion.
# Gelabelte Bild erzeugen
label_image, n_labels = measure.label(open_area_mask, return_num=True, connectivity=2)
plt.imshow(label_image)
plt.axis('off')
plt.title(f'Labeled Image, Number of objects: {n_labels}')
plt.gcf().set_size_inches(10, 10)
plt.show()
Hier wurden die einzelnen Oliven gelabelt.
Um die Eigenschaften der Oliven zu extrahieren, verwendet der Forscher die regionprops-Funktion. Er zeichnet auch gleich ein Rechteck um die Oliven.
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches
from skimage.measure import regionprops
from skimage.color import label2rgb
image_label_overlay = label2rgb(open_area_mask, image=cut_img, bg_label=0, colors=['green', 'black'])
fig, ax = plt.subplots(figsize=(10, 6))
ax.imshow(image_label_overlay)
for region in regionprops(label_image):
# take regions with large enough areas
if region.area >= 35000:
# draw rectangle around segmented coins
minr, minc, maxr, maxc = region.bbox
rect = mpatches.Rectangle(
(minc, minr),
maxc - minc,
maxr - minr,
fill=False,
edgecolor='red',
linewidth=2,
)
ax.add_patch(rect)
plt.legend(handles=[mpatches.Patch(color='green', label='Green Olive'), mpatches.Patch(color='black', label='Black Olive')])
ax.set_axis_off()
plt.tight_layout()
plt.gcf().set_size_inches(10, 10)
plt.title('Segmented Olives')
plt.show()
Die grünen Oliven sind nun erfolgreich von den schwarzen Oliven getrennt. Der Forscher hat die grünen Oliven erfolgreich segmentiert und die Eigenschaften der Oliven extrahiert.
Nun möchte er noch einige Eigenschaften der Oliven extrahieren. Dafür verwendet er die regionprops-Funktion.
properties = measure.regionprops(label_image)
data = []
counter = 0
for prop in properties:
if prop.area >= 35000:
data.append({
'Label': prop.label,
'Area': prop.area,
'Perimeter': prop.perimeter,
'Centroid': prop.centroid,
'Equivalent Diameter': prop.equivalent_diameter
})
counter += 1
df = pd.DataFrame(data)
df
| Label | Area | Perimeter | Centroid | Equivalent Diameter | |
|---|---|---|---|---|---|
| 0 | 1 | 90736.0 | 1168.028571 | (429.79281652265917, 640.3211955563393) | 339.895077 |
| 1 | 2 | 79720.0 | 1113.903679 | (470.64607375815353, 1483.8246487706974) | 318.594816 |
| 2 | 4 | 57277.0 | 948.648845 | (603.7537405939557, 1023.0633587652985) | 270.050627 |
| 3 | 8 | 56205.0 | 955.442784 | (774.357139044569, 401.4485899830976) | 267.511549 |
| 4 | 10 | 52596.0 | 941.116882 | (957.8327439349, 1605.2142748497984) | 258.780423 |
| 5 | 12 | 82139.0 | 1090.655988 | (1024.6517732136988, 1106.187998392968) | 323.392367 |
| 6 | 18 | 90926.0 | 1158.229581 | (1133.2740690231617, 250.51732177814927) | 340.250759 |
| 7 | 20 | 55106.0 | 943.619408 | (1193.3942038979421, 788.5050085290168) | 264.883254 |
| 8 | 22 | 59874.0 | 1007.969696 | (1383.1843371079267, 1170.6805291111334) | 276.104952 |
| 9 | 35 | 96930.0 | 1205.793073 | (1531.4626431445372, 1673.781089445992) | 351.304866 |
| 10 | 43 | 97948.0 | 1221.224530 | (1557.8515130477397, 510.79752521746235) | 353.144824 |
| 11 | 48 | 59506.0 | 970.111832 | (1704.7328000537761, 1408.367710819077) | 275.255140 |
| 12 | 50 | 50069.0 | 902.371716 | (1770.6639637300525, 825.8605524376361) | 252.487288 |
Nun hat er einige Eigenschaften auslesen können. Er möchte aber die Oliven nach ihrer Farbe klassifizieren.
plt.imshow(image_label_overlay)
for datapoint in data:
plt.text(datapoint['Centroid'][1], datapoint['Centroid'][0], f'Label: {datapoint["Label"]}', color='red')
plt.title('Segmented Olives with Labels')
plt.gcf().set_size_inches(10, 10)
plt.show()
Dazu musste der Forscher die Oliven manuell klassifizieren.
mapping = {1: 'Green Olive', 2: 'Green Olive', 4: 'Black Olive', 8: 'Black Olive', 10: 'Black Olive', 12: 'Green Olive', 18: 'Green Olive', 20: 'Black Olive', 22: 'Black Olive', 35: 'Green Olive', 43: 'Green Olive', 48: 'Black Olive', 50: 'Black Olive'}
df['Olive Type'] = df['Label'].map(mapping)
df
| Label | Area | Perimeter | Centroid | Equivalent Diameter | Olive Type | |
|---|---|---|---|---|---|---|
| 0 | 1 | 90736.0 | 1168.028571 | (429.79281652265917, 640.3211955563393) | 339.895077 | Green Olive |
| 1 | 2 | 79720.0 | 1113.903679 | (470.64607375815353, 1483.8246487706974) | 318.594816 | Green Olive |
| 2 | 4 | 57277.0 | 948.648845 | (603.7537405939557, 1023.0633587652985) | 270.050627 | Black Olive |
| 3 | 8 | 56205.0 | 955.442784 | (774.357139044569, 401.4485899830976) | 267.511549 | Black Olive |
| 4 | 10 | 52596.0 | 941.116882 | (957.8327439349, 1605.2142748497984) | 258.780423 | Black Olive |
| 5 | 12 | 82139.0 | 1090.655988 | (1024.6517732136988, 1106.187998392968) | 323.392367 | Green Olive |
| 6 | 18 | 90926.0 | 1158.229581 | (1133.2740690231617, 250.51732177814927) | 340.250759 | Green Olive |
| 7 | 20 | 55106.0 | 943.619408 | (1193.3942038979421, 788.5050085290168) | 264.883254 | Black Olive |
| 8 | 22 | 59874.0 | 1007.969696 | (1383.1843371079267, 1170.6805291111334) | 276.104952 | Black Olive |
| 9 | 35 | 96930.0 | 1205.793073 | (1531.4626431445372, 1673.781089445992) | 351.304866 | Green Olive |
| 10 | 43 | 97948.0 | 1221.224530 | (1557.8515130477397, 510.79752521746235) | 353.144824 | Green Olive |
| 11 | 48 | 59506.0 | 970.111832 | (1704.7328000537761, 1408.367710819077) | 275.255140 | Black Olive |
| 12 | 50 | 50069.0 | 902.371716 | (1770.6639637300525, 825.8605524376361) | 252.487288 | Black Olive |
Eigenschaften der Oliven¶
Mit dieser neuen Information kann der Forscher analysen aufgrund der Farbe der Oliven durchführen. Beispielsweise kann er den Mittelwert der Fläche pro Olivenart berechnen.
# Plot mean are per olive type
df.groupby('Olive Type')['Area'].mean().plot(kind='bar')
plt.xlabel('Olive Type')
plt.ylabel('Mean Pixel Area')
plt.title('Mean Pixel Area per Olive Type')
plt.show()
print(df.groupby('Olive Type')['Area'].mean())
Olive Type Black Olive 55804.714286 Green Olive 89733.166667 Name: Area, dtype: float64
Der Forscher hat so herausgefunden das die grünen Oliven im Durchschnitt viel grösser sind als die Schwarzen. Das ist eine wichtige Information um die Oliven automatisch trennen zu können.
Ausserdem hat der Forscher die Anzahl der Oliven pro Typ geplottet.
#plot count of olives per type
df['Olive Type'].value_counts().plot(kind='bar')
plt.xlabel('Olive Type')
plt.ylabel('Count')
plt.title('Count of Olives per Type')
plt.show()
Mit dem Plot kann er zeigen, dass es mehr schwarze Oliven als grüne Oliven auf dem Bild gibt. Insgesammt sind es 13 Oliven.
Die meisten Eigenschaften sind nicht so aussagekräftig um die Oliven besser trennen zu können. Der Ausschlaggebende Punkt ist die Fläche der Oliven. Die grünen Oliven sind im Durchschnitt viel grösser als die schwarzen Oliven.
Nun zeichnet der Forscher noch das Skeleton der Oliven.
# Skeletonisierung eines Objekts
original_image = properties[11].image
skeleton = morphology.skeletonize(original_image)
skeleton_pixel_count = np.sum(skeleton)
# Ergebnisse diskutieren
print(f'Anzahl der Pixel des Skeletons: {skeleton_pixel_count}')
# subplot original and skeleton
fig, ax = plt.subplots(1, 2, figsize=(10, 5))
ax[0].imshow(original_image, cmap='gray')
ax[0].set_title('Original Image')
ax[0].axis('off')
ax[1].imshow(skeleton, cmap='gray')
ax[1].set_title('Skeleton')
ax[1].axis('off')
plt.show()
Anzahl der Pixel des Skeletons: 145
Parameterwahl¶
Der Forscher hat bei den morphologischen Operationen hauptsächlich einen Disk-Kernel verwendet. Der Disk-Kernel ist hier eine gute Wahl, da die Oliven eine ovale Form haben.
plt.imshow(morphology.disk(15))
plt.title('Disk Kernel der Größe 15')
plt.show()
Diskussion¶
Der Forscher ist mit seiner Arbeit allgemein sehr zu Frieden. Er konnte die grünen Oliven von den schwarzen Oliven trennen und so die Abfüllung von grünen Oliven zu den schwarzen Oliven verhindern. Die morphologischen Operationen haben gut funktioniert. Auch wurden die segmentierten Objekte durch die Operationen kaum verändert oder verschoben.
Keypoint Matching mit ORB¶
ORB mit unterschiedlichen Bildern¶
Ein bekannter Handyhersteller möchte eine App entwickeln, die es ermöglicht, die selbst aufgenommenen Bilder zu klassifizieren. Das selbe Objekt soll in unterschiedlichen Positionen und Winkeln erkannt werden. Dafür möchte der Handyhersteller den ORB-Algorithmus verwenden.
Ich verwende hier die PIL-Library zum Importieren der Bilder, da es mit OpenCV nicht funktioniert hat. Alle folgenden Bilder habe ich selber aufgenommen.
import os
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
# Load images in list
images = []
path = 'Fotos_Glas/'
# for file in os.listdir(path):
for img in os.listdir(path):
img = Image.open(path + img)
img_rgb = img.convert('RGB')
img_rotated = img_rgb.rotate(-90, expand=True)
images.append(np.array(img_rotated))
# plot all images
fig, ax = plt.subplots(1, len(images), figsize=(15, 20))
for i, image in enumerate(images):
ax[i].imshow(image)
plt.show()
Dies sind meine Bilder des selben Objektes aus unterschiedlichen Aufnahmewinkeln. Das Objekt ist ein Shotglas aus Griechenland. Es ist mit dem Nazar-Auge (https://de.wikipedia.org/wiki/Nazar-Amulett) verziert.
Der Handyhersteller hat zuerst einfach mal Code aus dem Internet kopiert und an seine Bedürfnisse angepasst.
import os
from PIL import Image
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
query_img = Image.open('Fotos_Glas/20240531_140048.jpg')
train_img = Image.open('Fotos_Glas/train_img.jpg')
query_img = np.array(query_img.convert('RGB'))
train_img = np.array(train_img.convert('RGB'))
# Rotate
query_img = cv.rotate(query_img, cv.ROTATE_90_CLOCKWISE)
train_img = cv.rotate(train_img, cv.ROTATE_90_CLOCKWISE)
# Convert it to grayscale
query_img_bw = cv.cvtColor(query_img,cv.COLOR_BGR2GRAY)
train_img_bw = cv.cvtColor(train_img, cv.COLOR_BGR2GRAY)
# Initialize the ORB detector algorithm
orb = cv.ORB_create()
# Now detect the keypoints and compute
# the descriptors for the query image
# and train image
queryKeypoints, queryDescriptors = orb.detectAndCompute(query_img_bw,None)
trainKeypoints, trainDescriptors = orb.detectAndCompute(train_img_bw,None)
# Initialize the Matcher for matching
# the keypoints and then match the
# keypoints
matcher = cv.BFMatcher()
matches = matcher.match(queryDescriptors,trainDescriptors)
# draw the matches to the final image
# containing both the images the drawMatches()
# function takes both images and keypoints
# and outputs the matched query image with
# its train image
final_img = cv.drawMatches(query_img, queryKeypoints,
train_img, trainKeypoints, matches[:20],None, matchColor=[0,0,0])
final_img = cv.resize(final_img, (3000,3500))
# Show the final image
plt.imshow(final_img)
plt.gcf().set_size_inches(10, 10)
plt.show()
Quelle: https://www.geeksforgeeks.org/feature-matching-using-orb-algorithm-in-python-opencv/
Der Handyhersteller ist aber mit dem Ergebnis nicht zufrieden. Er möchte die Bilder besser vergleichen und die Übereinstimmungen besser darstellen. Dazu schreibt er eine eigene Funktion, die die Matches besser erkennt und darstellt.
def search_matches(query_img, train_img):
query_img = np.array(query_img.convert('RGB'))
train_img = np.array(train_img.convert('RGB'))
query_img = cv.rotate(query_img, cv.ROTATE_90_CLOCKWISE)
train_img = cv.rotate(train_img, cv.ROTATE_90_CLOCKWISE)
query_img_bw = cv.cvtColor(query_img, cv.COLOR_BGR2GRAY)
train_img_bw = cv.cvtColor(train_img, cv.COLOR_BGR2GRAY)
orb = cv.ORB_create()
queryKeypoints, queryDescriptors = orb.detectAndCompute(query_img_bw, None)
trainKeypoints, trainDescriptors = orb.detectAndCompute(train_img_bw, None)
matcher = cv.BFMatcher()
matches = matcher.match(queryDescriptors, trainDescriptors)
matches = sorted(matches, key=lambda x: x.distance)
final_img = cv.drawMatches(query_img, queryKeypoints, train_img, trainKeypoints, matches[:20], None,
matchColor=(0, 0, 0), flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
return final_img
import os
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
# Load images in list
images = []
path = 'Fotos_Glas/'
# for file in os.listdir(path):
for img in os.listdir(path):
img = Image.open(path + img)
images.append(img)
# plot all images with matches
train_img = Image.open('Fotos_Glas/train_img.jpg')
fig, ax = plt.subplots(len(images), 1, figsize=(50, 25))
for i, image in enumerate(images):
final_img = search_matches(image, train_img)
ax[i].imshow(final_img)
ax[i].axis('off')
plt.show()
Das unterste Bild zeigt auf beiden Seiten das gleiche Bild. Deswegen erkennt der ORB-Algorithmus eine klare Übereinstimmung.
Da die Bilder jedoch sonst nur sehr schelcht erkannt wurden, möchte der Handyhersteller die Helligkeit der Bilder anpassen.
import os
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
from PIL import ImageEnhance
# Load images in list
images = []
path = 'Fotos_Glas/'
# for file in os.listdir(path):
for img in os.listdir(path):
img = Image.open(path + img)
# change the brightness of the image
enhancer = ImageEnhance.Brightness(img)
images.append(enhancer.enhance(1.5))
train_img = Image.open('Fotos_Glas/train_img.jpg')
fig, ax = plt.subplots(len(images), 1, figsize=(50, 25))
for i, image in enumerate(images):
final_img = search_matches(image, train_img)
ax[i].imshow(final_img)
ax[i].axis('off')
plt.show()
Die Helligkeitsanpassung hat die Erkennung nicht massgeblich verbessert. Der Handyhersteller ist mit dem Ergebnis nicht zufrieden und möchte die Bilder noch weiter anpassen.
import os
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
from PIL import ImageEnhance
# Load images in list
images = []
path = 'Fotos_Glas/'
# for file in os.listdir(path):
for img in os.listdir(path):
img = Image.open(path + img)
# change the brightness of the image
enhancer = ImageEnhance.Brightness(img)
images.append(enhancer.enhance(0.5))
train_img = Image.open('Fotos_Glas/train_img.jpg')
fig, ax = plt.subplots(len(images), 1, figsize=(25, 25))
for i, image in enumerate(images):
final_img = search_matches(image, train_img)
ax[i].imshow(final_img)
ax[i].axis('off')
plt.show()
Die Helligkeitsanpassung hat die Erkennung verschlechtert. Dunklere Bilder sind also für den ORB-Algorithmus nicht geeignet.
import os
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
from PIL import ImageEnhance
# Load images in list
images = []
path = 'Fotos_Glas/'
# for file in os.listdir(path):
for img in os.listdir(path):
img = Image.open(path + img)
# change the brightness of the image
enhancer = ImageEnhance.Brightness(img)
images.append(enhancer.enhance(1.8))
enhancer = ImageEnhance.Brightness(Image.open('Fotos_Glas/train_img.jpg'))
train_img = enhancer.enhance(1.8)
fig, ax = plt.subplots(len(images), 1, figsize=(25, 25))
for i, image in enumerate(images):
final_img = search_matches(image, train_img)
ax[i].imshow(final_img)
ax[i].axis('off')
plt.show()
Auch sehr helle Bilder sind für den ORB-Algorithmus nicht geeignet. Die Schlussfolgerung des Handyherstellers ist, dass der ORB-Algorithmus für unterschiedliche Winkel des gleichen Objektes nicht geeignet ist. Er wird sich nun nach einer anderen Lösung umsehen.
Beschreibung ORB¶
Der erste Schritt ist, die Bilder zu importieren und in Graustufenbilder umzuwandeln. Man benötigt zwei Bilder einmal das Query-Bild und das Trainingsbild. Das Query-Bild muss dann im Trainingsbild gefunden werden.
from PIL import Image
import numpy as np
import cv2 as cv
import matplotlib.pyplot as plt
query_img = Image.open('Fotos_Glas/20240531_140048.jpg')
train_img = Image.open('Fotos_Glas/train_img.jpg')
query_img = np.array(query_img.convert('RGB'))
train_img = np.array(train_img.convert('RGB'))
query_img = cv.rotate(query_img, cv.ROTATE_90_CLOCKWISE)
train_img = cv.rotate(train_img, cv.ROTATE_90_CLOCKWISE)
# Convert it to grayscale
query_img_bw = cv.cvtColor(query_img,cv.COLOR_BGR2GRAY)
train_img_bw = cv.cvtColor(train_img, cv.COLOR_BGR2GRAY)
Die Bilder werden importiert und in ein NumPy-Array umgewandelt. Anschliessend werden die Bilder um 90 Grad gedreht und in Graustufen umgewandelt.
orb = cv.ORB_create()
queryKeypoints, queryDescriptors = orb.detectAndCompute(query_img_bw,None)
trainKeypoints, trainDescriptors = orb.detectAndCompute(train_img_bw,None)
Der ORB-Algorithmus wird initialisiert und die Keypoints und Descriptoren für das Query- und Trainingsbild werden berechnet.
# show keypoints in query image
img_keypoints = cv.drawKeypoints(query_img, queryKeypoints, 0, (0, 0, 255), flags=cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
plt.figure(figsize=(15,15))
plt.imshow(img_keypoints)
plt.title(f'Query Image, {len(queryKeypoints)} Keypoints')
plt.axis('off')
plt.show()
Die Keypoints im Query-Bild werden angezeigt. Je grösser der Kreis, desto wichtiger ist der Keypoint. Die Keypoints finden sich meist an Ecken oder Kanten.
# show keypoints in train image
img_keypoints = cv.drawKeypoints(train_img, trainKeypoints, 0, (0, 0, 255), flags=cv.DRAW_MATCHES_FLAGS_DRAW_RICH_KEYPOINTS)
plt.figure(figsize=(15,15))
plt.imshow(img_keypoints)
plt.title(f'Train Image, {len(trainKeypoints)} Keypoints')
plt.axis('off')
plt.show()
Auch beim Trainingsbild werden die Keypoints angezeigt.
Die Descriptoren sind die Beschreibungen der Keypoints. Sie enthalten Informationen über die Umgebung des Keypoints.
plt.imshow(queryDescriptors, cmap='gray', aspect='auto')
plt.xlabel('Descriptor Bits')
plt.ylabel('Keypoints')
plt.colorbar()
plt.show()
plt.imshow(trainDescriptors, cmap='gray', aspect='auto')
plt.xlabel('Descriptor Bits')
plt.ylabel('Keypoints')
plt.colorbar()
plt.show()
Die Descriptoren der Keypoints sagen von blosem Auge nicht viel aus.
Mit dem Brute-Force-Matcher werden die Matches zwischen den Descriptoren der Keypoints berechnet.
matcher = cv.BFMatcher()
matches = matcher.match(queryDescriptors,trainDescriptors)
matches = sorted(matches, key = lambda x:x.distance)
Um die Matches zu visualisieren, wird die Funktion drawMatches verwendet. Sie zeichnet Linien zwischen den Matches. Es werden hier die ersten 50 Matches gezeichnet.
final_img = cv.drawMatches(query_img, queryKeypoints, train_img, trainKeypoints, matches[:50], None, matchColor=(0,0,0))
plt.figure(figsize=(25,15))
plt.imshow(final_img)
plt.axis('off')
plt.show()
Die Matches werden in einem Bild dargestellt. Die Linien verbinden die Matches zwischen den Keypoints. Die Linien sind länger, je schlechter die Übereinstimmung ist.
ORB mit rotiertem Bild/Bildausschnitt¶
Nun möchte der Handyhersteller den ORB-Algorithmus mit rotierten Bildern testen.
def search_matches(query_img, train_img):
query_img = np.array(query_img.convert('RGB'))
train_img = np.array(train_img.convert('RGB'))
query_img = cv.rotate(query_img, cv.ROTATE_90_CLOCKWISE)
train_img = cv.rotate(train_img, cv.ROTATE_90_CLOCKWISE)
query_img_bw = cv.cvtColor(query_img, cv.COLOR_BGR2GRAY)
train_img_bw = cv.cvtColor(train_img, cv.COLOR_BGR2GRAY)
orb = cv.ORB_create()
queryKeypoints, queryDescriptors = orb.detectAndCompute(query_img_bw, None)
trainKeypoints, trainDescriptors = orb.detectAndCompute(train_img_bw, None)
matcher = cv.BFMatcher()
matches = matcher.match(queryDescriptors, trainDescriptors)
matches = sorted(matches, key=lambda x: x.distance)
final_img = cv.drawMatches(query_img, queryKeypoints, train_img, trainKeypoints, matches[:20], None,
matchColor=(0, 0, 0), flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
return final_img
Der Handyhersteller probiert nun den ORB-Algorithmus auf das selbe Bild aber rotiert in dem Originalbild aus.
import os
from PIL import Image
import numpy as np
import matplotlib.pyplot as plt
from PIL import ImageEnhance
train_img = Image.open('Fotos_Glas/train_img.jpg')
images = []
# generate random rotations of the train image
for i in range(4):
img = train_img.rotate(i*90)
images.append(img)
fig, ax = plt.subplots(len(images), 1, figsize=(25, 50))
for i, image in enumerate(images):
final_img = search_matches(image, train_img)
ax[i].imshow(final_img)
ax[i].axis('off')
plt.show()
Der ORB-Algorithmus funktioniert hier perfekt. Die Matches werden in allen Bildern gefunden. Der Handyhersteller ist mit der Leistung des ORB-Algorithmus zufrieden.
Nun möchte er den ORB-Algorithmus auf ein ein Bildausschnitt testen.
train_img = Image.open('Fotos_Glas/train_img.jpg')
images = []
#generate random snippets of the train image
cutout_areas = [(1500, 400, 2000, 700), (2000, 100, 2500, 500), (1000, 100, 3000, 500), (2800, 200, 3500, 1400)]
for i in range(4):
randomImage = train_img.crop(cutout_areas[i])
images.append(randomImage)
fig, ax = plt.subplots(len(images), 1, figsize=(25, 50))
for i, image in enumerate(images):
final_img = search_matches(image, train_img)
ax[i].imshow(final_img)
ax[i].axis('off')
plt.show()
Dabei ist zu erkennen, dass der ORB-Algorithmus auch bei Bildausschnitten gut funktioniert. Doch bei dem Ausschnitt mit wenigen Nazar-Augen wird der exakte Match nicht gefunden.
Vergleich zu SIFT¶
def search_matches_sift(query_img, train_img):
query_img = np.array(query_img.convert('RGB'))
train_img = np.array(train_img.convert('RGB'))
query_img = cv.rotate(query_img, cv.ROTATE_90_CLOCKWISE)
train_img = cv.rotate(train_img, cv.ROTATE_90_CLOCKWISE)
query_img_bw = cv.cvtColor(query_img, cv.COLOR_BGR2GRAY)
train_img_bw = cv.cvtColor(train_img, cv.COLOR_BGR2GRAY)
sift = cv.SIFT_create()
queryKeypoints, queryDescriptors = sift.detectAndCompute(query_img_bw, None)
trainKeypoints, trainDescriptors = sift.detectAndCompute(train_img_bw, None)
matcher = cv.BFMatcher()
matches = matcher.match(queryDescriptors, trainDescriptors)
matches = sorted(matches, key=lambda x: x.distance)
final_img = cv.drawMatches(query_img, queryKeypoints, train_img, trainKeypoints, matches[:20], None,
matchColor=(0, 0, 0), flags=cv.DrawMatchesFlags_NOT_DRAW_SINGLE_POINTS)
return final_img
# Load images in list
images = []
path = 'Fotos_Glas/'
for img in os.listdir(path):
img = Image.open(path + img)
images.append(img)
# plot all images with matches
train_img = Image.open('Fotos_Glas/train_img.jpg')
fig, ax = plt.subplots(len(images), 1, figsize=(50, 25))
for i, image in enumerate(images):
final_img = search_matches_sift(image, train_img)
ax[i].imshow(final_img)
ax[i].axis('off')
plt.show()
Der Handyhersteller hat den SIFT-Algorithmus verwendet. Der SIFT-Algorithmus ist ein von Auge ein wenig besser als der ORB-Algorithmus. Er erkennt die Matches auf Bilder mit unterschiedlichen Winkeln besser.